package cc.shacocloud.mirage.utils.resource;

import cc.shacocloud.mirage.utils.ClassUtil;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Objects;

/**
 * 类路径资源对象
 *
 * @author 思追(shaco)
 */
public class ClassPathResource extends AbstractFileResolvingResource {
    
    @NotNull
    @Getter
    private final String path;
    
    @Nullable
    private ClassLoader classLoader;
    
    @Nullable
    private Class<?> clazz;
    
    @Nullable
    private URL url;
    
    private volatile boolean notResolved = true;
    
    public ClassPathResource(@NotNull String path) {
        this(path, (ClassLoader) null);
    }
    
    public ClassPathResource(@NotNull String path, @Nullable ClassLoader classLoader) {
        String pathToUse = ResourceUtil.normalize(path);
        if (pathToUse.startsWith("/")) {
            pathToUse = pathToUse.substring(1);
        }
        this.path = pathToUse;
        this.classLoader = (classLoader != null ? classLoader : ClassUtil.getDefaultClassLoader());
    }
    
    public ClassPathResource(@NotNull String path, @Nullable Class<?> clazz) {
        this.path = ResourceUtil.normalize(path);
        this.clazz = clazz;
    }
    
    /**
     * 此实现检查资源 URL 的解析
     *
     * @see java.lang.ClassLoader#getResource(String)
     * @see java.lang.Class#getResource(String)
     */
    @Override
    public boolean exists() {
        return (resolveURL() != null);
    }
    
    /**
     * 此实现预先检查资源 URL 的解析，然后继续执行 {@link AbstractFileResolvingResource#checkReadable(URL)} 的长度检查。
     *
     * @see java.lang.ClassLoader#getResource(String)
     * @see java.lang.Class#getResource(String)
     */
    @Override
    public boolean isReadable() {
        URL url = resolveURL();
        return (url != null && checkReadable(url));
    }
    
    @Override
    public URL getURL() throws IOException {
        return resolveURL();
    }
    
    /**
     * 解析基础类路径资源的 URL
     *
     * @return 解析的 URL，如果不可解析，则为 {@code null}
     */
    @Nullable
    protected URL resolveURL() {
        if (Objects.isNull(this.url) && notResolved) {
            try {
                if (this.clazz != null) {
                    this.url = this.clazz.getResource(this.path);
                } else if (this.classLoader != null) {
                    this.url = this.classLoader.getResource(this.path);
                } else {
                    this.url = ClassLoader.getSystemResource(this.path);
                }
            } catch (IllegalArgumentException ignore) {
                // 根据 JDK 的文档，不应发生：
                // see https://github.com/openjdk/jdk/pull/2662
            }
            
            this.notResolved = false;
        }
        
        return this.url;
    }
    
    
    @Override
    public InputStream getStream() throws IOException {
        InputStream is;
        if (this.clazz != null) {
            is = this.clazz.getResourceAsStream(this.path);
        } else if (this.classLoader != null) {
            is = this.classLoader.getResourceAsStream(this.path);
        } else {
            is = ClassLoader.getSystemResourceAsStream(this.path);
        }
        if (is == null) {
            throw new FileNotFoundException(getDescription() + " 无法打开，因为它不存在");
        }
        return is;
    }
    
    @Override
    public String getDescription() {
        StringBuilder builder = new StringBuilder("class path resource [");
        String pathToUse = this.path;
        if (this.clazz != null && !pathToUse.startsWith("/")) {
            builder.append(ClassUtil.classPackageAsResourcePath(this.clazz));
            builder.append('/');
        }
        if (pathToUse.startsWith("/")) {
            pathToUse = pathToUse.substring(1);
        }
        builder.append(pathToUse);
        builder.append(']');
        return builder.toString();
    }
    
    /**
     * 返回将从中获取此资源的类加载器
     */
    @Nullable
    public final ClassLoader getClassLoader() {
        return (this.clazz != null ? this.clazz.getClassLoader() : this.classLoader);
    }
}
